import WebSocket from 'ws';
import { Service } from '../utils/mysqlHandler';
import logger from '../logger';
import Message from './Message';
namespace Agent {
    export enum State {
        CONNECTED = 1,
        CONNECTING,
        DISCONNECTING,
        DISCONNECTED,
        ERROR,
    };
    type onErrorCallback = (code: number, errmsg: string) => void;
    type onAgentMsgCallback = (data: string) => void;
    export class AgentClient {
        private wsc: WebSocket | null = null; // 与Agent连接的套接字
        private serviceInfo: Service;
        private state: State = State.ERROR;
        private errmsg: string = '';
        private timerId: NodeJS.Timeout | null = null;
        private onErrorCb: onErrorCallback;
        private onAgentMsgCb: onAgentMsgCallback;

        constructor(serviceInfo: Service, onErrorCb: onErrorCallback, onAgentMsgCb: onAgentMsgCallback) {
            this.serviceInfo = serviceInfo;
            this.onErrorCb = onErrorCb;
            this.onAgentMsgCb = onAgentMsgCb;
            this.connect();
        }

        /**
         * 函数功能:        尝试连接到agent, 如果已经有连接则断开之前的连接.
         */
        private connect() {
            if (this.state === State.CONNECTING) {
                return;
            }
            this.close();
            this.state = State.CONNECTING;
            try {
                if (!this.serviceInfo.agentAddr) {
                    logger.error('invalid agentAddr, agentAddr: ', this.serviceInfo.agentAddr);
                    return;
                }
                this.wsc = new WebSocket(this.serviceInfo.agentAddr);
            }
            catch (e) {
                this.state = State.ERROR;
                logger.info('error errmsg: ', e.message);
                this.setStatus(-1, e.message);
                return;
            }

            this.wsc.on('open', () => {
                // 连接成功
                this.onOpen();
            });

            this.wsc.on('close', (code: number, reason: string) => {
                this.onClose(code, reason);
            });

            this.wsc.on('message', (data: WebSocket.Data) => {
                this.onMessage(<string>(data));
            });
            this.wsc.on('error', (err: Error) => {
                this.onError(err);
            });
        }
        private reconnect() {
            if (this.timerId) {
                return;
            }
            if (this.wsc) {
                // 既然要重新连接了,那么之前注册的事件都不在需要了, 否则当主动断开连接的时候还会收到onclose事件.
                this.wsc.removeAllListeners();
            }
            this.timerId = setTimeout(() => {
                this.timerId = null;
                this.connect();
            }, 1000 * 3);
        }

        private onOpen() {
            this.state = State.CONNECTED;
            this.setStatus(1, 'ok');
            this.sendServiceInfo();
        }
        private onClose(code: number, reason: string) {
            this.setStatus(code, 'closed.');
            // 本来连接建立正常的,后来由于服务端重启导致断开连接了,则调用这里
            this.reconnect();
        }
        /**
         * 函数功能：           处理来自agent的消息（可能是WxService的消息也可能是Agent状态消息）
         * @param data 
         */
        private onMessage(data: string) {
            logger.info('onMessage data: ', data);
            this.onAgentMsgCb(data);
        }
        private onError(err: Error) {
            this.state = State.DISCONNECTED;
            // 连接的时候,如果连接服务器失败了,则调用这里
            this.setStatus(State.DISCONNECTED, err.message);
            this.reconnect();
        }

        setStatus(code: number, errmsg: string) {
            if (this.errmsg === errmsg) {
                return;
            }
            logger.info('serviceId: ', this.serviceInfo.serviceId, ' status changed status: ', code, ' msg: ', errmsg, ' agent: ', JSON.stringify(this.serviceInfo));
            this.state = code;
            this.errmsg = errmsg;
            this.onErrorCb(this.state, this.errmsg);
        }
        send(data: string): boolean {
            if (this.state !== State.CONNECTED) {
                logger.error('state error. cod: ', this.state, ' errmsg: ', this.errmsg);
                return false;
            }
            if (!this.wsc) {
                logger.error('websocket is null. data: ', data);
                return false;
            }
            this.wsc.send(data, (err?: Error) => {
                if (err) {
                    // 发送失败了.
                    logger.error('message send faled. errmsg: ', err.message, ' data: ', data);
                }
            });
            return true;
        }




        close() {
            if (this.timerId) {
                clearTimeout(this.timerId);
                this.timerId = null;
            }
            if (this.wsc) {
                this.wsc.removeAllListeners();
                this.wsc.terminate();
                this.wsc = null;
            }
        }


        sendServiceInfo() {
            if (!this.wsc) {
                logger.error('websocket is null. service: ', this.serviceInfo);
                return;
            }
            let agentInfoReq: Message.AgentInfoReq = new Message.AgentInfoReq();
            agentInfoReq.method = Message.Method.AGENT_INFO_REQ;
            agentInfoReq.redisCluster = this.serviceInfo.redisCluster;
            agentInfoReq.mysqlAddr = this.serviceInfo.mysqlAddr;
            agentInfoReq.mysqlPort = this.serviceInfo.mysqlPort;
            agentInfoReq.mysqlUser = this.serviceInfo.mysqlUser;
            agentInfoReq.mysqlPwd = this.serviceInfo.mysqlPwd;
            agentInfoReq.mysqlDbname = this.serviceInfo.mysqlDbname;
            agentInfoReq.wxServiceAddr = this.serviceInfo.wxServiceAddr;
            agentInfoReq.description = this.serviceInfo.description;
            if (this.wsc.readyState !== this.wsc.OPEN) {
                logger.error("socket is not ready.");
                return;
            }
            this.wsc.send(JSON.stringify(agentInfoReq));
        }
        updateServiceInfo(serviceInfo: Service) {
            // 重新设置服务信息
            // 1. 检查agentAddr是否已经改变
            // 1.1 如果agentAddr改变了,则需要断开之前的连接重新连接到新的agent
            // 2. 把更新后的agent信息发送给agent.
            let agentAddrChanged: boolean = false;
            if (this.serviceInfo.agentAddr !== serviceInfo.agentAddr) {
                agentAddrChanged = true;
            }
            this.serviceInfo = serviceInfo;
            if (agentAddrChanged) {
                if (this.timerId) {
                    logger.info('clearTimeout');
                    clearTimeout(this.timerId);
                    this.timerId = null;
                }
                this.state = State.DISCONNECTING;
                this.connect();
            }
            else {
                this.sendServiceInfo();
            }

            logger.info('updateServiceInfo: ', this.serviceInfo);

        }

        getState(): State {
            return this.state;
        }
        getServiceInfo() {
            return this.serviceInfo;
        }
    }
}

export = Agent;